home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Classes / PortComm / PortComm.m < prev    next >
Encoding:
Text File  |  1992-08-17  |  11.8 KB  |  417 lines

  1. // -------------------------------------------------------------------------------------
  2. // PortComm.m
  3. // serial port communication handler
  4. // Author: Steve Herrick, Martin D. Flynn, NeXT Computer, Inc.
  5. // -------------------------------------------------------------------------------------
  6. // THIS CODE FRAGMENT IS FOR DEMONSTRATION PURPOSES ONLY.
  7. // Permission is granted to freely redistribute this source code, and to use fragments
  8. // of this code in your own applications if you find them to be useful.  This class,
  9. // along with the source code, come with no warranty of any kind, and the user assumes
  10. // all responsibility for its use.
  11. // -------------------------------------------------------------------------------------
  12.  
  13. // -------------------------------------------------------------------------------------
  14. #import <stdio.h>
  15. #import <fcntl.h>
  16. #import <string.h>
  17. #import <libc.h>
  18. #import <ctype.h>
  19. #import <mach/cthreads.h>
  20. #import <sys/types.h>
  21. #import <sys/time.h>
  22. #import <sys/uio.h>
  23. #import <sys/ioctl.h>
  24. #import <sys/socket.h>
  25. #import <netinet/in.h>
  26. #import <netdb.h>
  27. #import <objc/hashtable.h>
  28. #import "PortComm.h"
  29.  
  30. // -------------------------------------------------------------------------------------
  31. #define         EOL(C)          (((C) == '\n') || ((C) == '\r'))
  32. #define            mutexAlloc        mutex_alloc
  33. #define            mutexFree(X)    mutex_free(X)
  34. #define            mutexLock(X)    mutex_lock(X)
  35. #define            mutexUnlock(X)    mutex_unlock(X)
  36.  
  37. // -------------------------------------------------------------------------------------
  38. @implementation PortComm
  39.  
  40. // -------------------------------------------------------------------------------------
  41. // forward/external declarations
  42. extern int              close(int fd);
  43.  
  44. // -------------------------------------------------------------------------------------
  45. static BOOL                debug = NO;                        // debug mode
  46. static int              openError = noERROR;            // global error number
  47.  
  48. // -------------------------------------------------------------------------------------
  49. #define                 validPORT(N)            (((N) >= 0) && ((N) <= 1))
  50. static char             *portDeviceTable[] = { portA, portB, 0 };
  51.                           
  52. // -------------------------------------------------------------------------------------
  53. // new instance
  54.  
  55. /* new serial port instance */
  56. + newPort:(int)portNum baud:(u_char)baudRate parity:(u_short)parity charMask:(u_char)mask
  57. {
  58.   
  59.   char                *portDev;
  60.   BOOL                eolCheck = YES;
  61.   int                ioSet, fd = -1;
  62.   struct sgttyb        sg;
  63.   struct ltchars    lc;
  64.   
  65.   /* single pass loop */
  66.   for (openError = noERROR;;) {
  67.   
  68.     /* open serial port device */
  69.     portDev = (char *)[PortComm getPortDevice:portNum];
  70.     if (!portDev) { openError = errorBADPORTNUM; break; }
  71.     if (debug) printf("PortComm: openning serial port #%d (%s)\n", portNum, portDev);
  72.     fd = open(portDev, O_RDWR);
  73.     if (fd < 0) { openError = errorOPEN; break; }
  74.     
  75.     /* prevent any other unauthorized port accesses */
  76.     if (ioctl(fd, TIOCEXCL, (void*)nil) < 0) { openError = errorTIOCEXCL; break; }
  77.  
  78.     /* force line discipline */
  79.     ioSet = OTTYDISC;
  80.     if (ioctl(fd, TIOCSETD, &ioSet) < 0) { openError = errorTIOCSETD; break; }
  81.  
  82.     /* set local word mode */
  83.     ioSet = LNOHANG /* | LPASS8OUT | LPASS8 */;
  84.     if (ioctl(fd, TIOCLSET, &ioSet) < 0) { openError = errorTIOCLSET; break; }
  85.     
  86.     /* force everything else we can think of */
  87.     if (ioctl(fd, TIOCCBRK , (void*)nil) < 0) { openError = errorTIOCCBRK ; break; }
  88.     if (ioctl(fd, TIOCSDTR , (void*)nil) < 0) { openError = errorTIOCSDTR ; break; }
  89.     if (ioctl(fd, TIOCNOTTY, (void*)nil) < 0) { openError = errorTIOCNOTTY; break; }
  90.  
  91.     /* set special chars */
  92.     memset(&lc, 0xFF, sizeof(lc));
  93.     if (ioctl(fd, TIOCSLTC, &lc) < 0) { openError = errorTIOCSLTC; break; }
  94.  
  95.     /* get current port configuration */
  96.     if (ioctl(fd, TIOCGETP, &sg) < 0) { openError = errorTIOCGETP; break; }
  97.  
  98.     /* turn off flags */
  99.     sg.sg_flags &= ~(ODDP | EVENP | ECHO | CBREAK | RAW | TANDEM);
  100.  
  101.     /* check parity */
  102.     eolCheck = (parity == parityRAW)? NO : YES;
  103.     if (parity == parityRAW_eol) parity = parityRAW;
  104.     
  105.     /* update port configuration */
  106.     memset(&sg, 0, sizeof(sg));
  107.     sg.sg_ispeed = baudRate;
  108.     sg.sg_ospeed = baudRate;
  109.     sg.sg_erase     = 0xFF;
  110.     sg.sg_kill     = 0xFF;
  111.     sg.sg_flags |= parity & (ODDP | EVENP | ANYP | RAW);    // set parity
  112.  
  113.     /* set new port configuration */
  114.     if (ioctl(fd, TIOCSETP, &sg) < 0) { openError = errorTIOCSETP; break; }
  115.     
  116.     /* break from single pass loop */
  117.     break;
  118.     
  119.   }
  120.  
  121.   /* check for error */
  122.   if (openError != noERROR) {
  123.     if (fd >= 0) close(fd);
  124.     if (debug) printf("PortComm: Unable to open serial port #%d\n", portNum);
  125.     return (id)nil;
  126.   }
  127.     
  128.   /* create instance */
  129.   self        = [[self alloc] init];
  130.   portFd      = fd;
  131.   portNumber  = portNum;
  132.   portBaud    = baudRate;
  133.   portParity  = parity;
  134.   checkEol      = eolCheck;
  135.   charMask      = (mask)? mask : 0xFF;
  136.   if (debug) printf("PortComm: Successfully opened serial port #%d\n", portNum);
  137.   
  138.   return self;
  139. }
  140.   
  141. + newPort:(int)portNum baud:(u_char)baudRate parity:(u_short)parity
  142. {
  143.   return [self newPort:portNum baud:baudRate parity:parity charMask:0];
  144. }
  145.  
  146. /* new instance of object */
  147. - init
  148. {
  149.   [super init];
  150.   isListening = NO;
  151.   rcdBuff     = (char*)nil;
  152.   dataHandler = (id)nil;
  153.   portFd      = -1;
  154.   portNumber  = -1;
  155.   portBaud    = B0;
  156.   portParity  = parityUNKNOWN;
  157.   portError   = noERROR;
  158.   forkMutex   = mutexAlloc();
  159.   writeMutex  = mutexAlloc();
  160.   maxBytes      = 1024;
  161.   charMask    = 0xFF;
  162.   checkEol    = YES;
  163.   return self;
  164. }
  165.   
  166. /* close/free port object */
  167. - free
  168. {
  169.   if (isListening) {
  170.       printf("WARNING: 'free' issued while serial port handler thread is active\n");
  171.     printf("         Application may terminate abnormally...\n");
  172.   }
  173.   close(portFd);
  174.   if (rcdBuff) free(rcdBuff);
  175.   mutexFree(forkMutex);
  176.   mutexFree(writeMutex);
  177.   return [super free];
  178. }
  179.  
  180. /* return port file descriptor */
  181. - (int)portFd
  182. {
  183.   return portFd;
  184. }
  185.   
  186. // -------------------------------------------------------------------------------------
  187. // convert baud rate to actual defined value
  188. + (u_char)baudRateConstant:(int)baudRate
  189. {
  190.   if (baudRate ==     0) return B0;
  191.   if (baudRate ==    50) return B50;
  192.   if (baudRate ==    75) return B75;
  193.   if (baudRate ==   110) return B110;
  194.   if (baudRate ==   134) return B134;
  195.   if (baudRate ==   150) return B150;
  196.   if (baudRate ==   200) return B200;
  197.   if (baudRate ==   300) return B300;
  198.   if (baudRate ==   600) return B600;
  199.   if (baudRate ==  1200) return B1200;
  200.   if (baudRate ==  1800) return B1800;
  201.   if (baudRate ==  2400) return B2400;
  202.   if (baudRate ==  4800) return B4800;
  203.   if (baudRate ==  9600) return B9600;
  204.   if (baudRate == 19200) return EXTA;
  205.   if (baudRate == 38400) return EXTB;
  206.   return B0;
  207. }
  208.   
  209. // -------------------------------------------------------------------------------------
  210. // return port device name for port number
  211. + (const char *)getPortDevice:(int)portNum
  212. {
  213.   return (validPORT(portNum))? portDeviceTable[portNum]: (char*)nil;
  214. }
  215.  
  216. // -------------------------------------------------------------------------------------
  217. // check portnumber validity
  218.  
  219. /* validate port number */
  220. + (BOOL)isValidPort:(int)portNum
  221. {
  222.   return (portNum >= 0) && (portNum <= 1);
  223. }
  224.  
  225. /* return port number */
  226. - (int)portNumber
  227. {
  228.   return portNumber;
  229. }
  230.   
  231. // -------------------------------------------------------------------------------------
  232. // set run flags
  233.  
  234. /* set data handler id */
  235. - setDataHandler:handler
  236. {
  237.   dataHandler = handler;
  238.   return self;
  239. }
  240.  
  241. /* set maximum read buffer size */
  242. - setMaxReadSize:(int)size
  243. {
  244.   maxBytes = size;
  245.   return self;
  246. }
  247.  
  248. /* set debug mode */
  249. - setDebugMode:(BOOL)debugMode
  250. {
  251.   debug = debugMode;
  252.   return self;
  253. }
  254.  
  255. /* set character mask */
  256. - setCharMask:(u_char)mask
  257. {
  258.   charMask = mask;
  259.   return self;
  260. }
  261.  
  262. // -------------------------------------------------------------------------------------
  263.  
  264. /* return port open error */
  265. + (int)openError
  266. {
  267.   return openError;
  268. }
  269.  
  270. /* return port error */
  271. - (int)portError
  272. {
  273.   return portError;
  274. }
  275.  
  276. // -------------------------------------------------------------------------------------
  277. // write data to port
  278.  
  279. - (int)writeToPort:(char *)buff
  280. {
  281.   return [self writeToPort:(char *)buff length:strlen(buff)];
  282. }
  283.  
  284. - (int)writeToPort:(char *)buff length:(int)buffLen
  285. {
  286.   int           writeLen;
  287.   mutexLock(writeMutex);
  288.   if (debug) printf("PortComm: writing '%s' (len=%d)\n", buff, buffLen);
  289.   writeLen = write(portFd, buff, buffLen);
  290.   mutexUnlock(writeMutex);
  291.   if (debug && (writeLen != buffLen)) printf("PortComm: write error! (%d)\n", writeLen);
  292.   return (writeLen == buffLen)? 0: -1;  
  293. }
  294.  
  295. // -------------------------------------------------------------------------------------
  296. // do timeout
  297. - (int)readTimeout:(float)timeout
  298. {
  299.   int                flag, charsReady;
  300.   u_long            uSec;
  301.   struct timeval    tm;
  302.   fd_set            portList;
  303.     
  304.   /* get number of character currently ready */
  305.   flag = ioctl(portFd, FIONREAD, &charsReady);
  306.   if (flag < 0) return flag;                    // if error
  307.   if (charsReady) return charsReady;            // if characters ready
  308.  
  309.   /* wait for character to be present */
  310.   if (debug) printf("PortComm: waiting for characters to be ready (%.1f)\n", timeout);
  311.   FD_ZERO(&portList);
  312.   FD_SET(portFd, &portList);
  313.   uSec = (u_long)(timeout * 1000000.0);
  314.   tm.tv_sec  = uSec / 1000000L;
  315.   tm.tv_usec = uSec % 1000000L;
  316.   flag = select(portFd + 1, &portList, 0, 0, &tm);
  317.   if (debug && (flag <  0)) printf("PortComm: character wait error! (%d)\n", flag); else
  318.   if (debug && (flag == 0)) printf("PortComm: character wait timeout!\n");
  319.  
  320.   /* return select results */
  321.   return flag;
  322.       
  323. }
  324.  
  325. // -------------------------------------------------------------------------------------
  326. // read data from port and return it in buff (null terminated)
  327. - (int)read:(char *)buff maxLen:(int)maxLen timeout:(float)timeout
  328. {
  329.   int    i, cnt;
  330.  
  331.   /* read from port */
  332.   for (i = cnt = 0; cnt < maxLen;) {
  333.   
  334.     /* timeout for characters ready */
  335.     if (timeout && ([self readTimeout:timeout] <= 0)) return -1;
  336.  
  337.     /* read data from port */
  338.     if (debug) printf("PortComm: waiting on read\n");
  339.     cnt += read(portFd, &buff[cnt], 1);    // maxLen - cnt - 1
  340.     if (charMask != 0xFF) for (;i < cnt; i++) buff[i] &= charMask;
  341.     if (checkEol && (cnt > 0) && EOL(buff[cnt - 1])) break;
  342.     
  343.   }
  344.   
  345.   /* remove trailing CR/LF (if not in raw mode) */
  346.   if (checkEol) {
  347.     for (;(cnt > 0) && EOL(buff[cnt - 1]); buff[--cnt] = 0);
  348.     buff[cnt] = 0;
  349.   }
  350.   if (debug) printf("PortComm: read '%s'\n", buff);
  351.  
  352.   return cnt;
  353. }
  354.  
  355. // -------------------------------------------------------------------------------------
  356. // read data from port and send it to 'record' method
  357.  
  358. /* thread function */
  359. static void readLoop(id self)
  360. {
  361.   [self readLoop:0.0];
  362.   cthread_exit(0);
  363.   }
  364.  
  365. - readLoop:(float)timeout
  366. {
  367.   int           cnt;
  368.   
  369.   /* allocate record buffer */
  370.   mutexLock(forkMutex);
  371.   if (rcdBuff) free(rcdBuff);
  372.   rcdBuff = (char*)malloc(maxBytes + 1);
  373.   mutexUnlock(forkMutex);
  374.   
  375.   /* read loop */
  376.   for (;;) {
  377.     cnt = [self read:rcdBuff maxLen:maxBytes timeout:timeout];
  378.     mutexLock(forkMutex);
  379.     [self dataRecord:rcdBuff len:cnt fromPort:portNumber];
  380.     mutexUnlock(forkMutex);
  381.   }
  382.     
  383.   /* return (never executed) */
  384.   isListening = NO;
  385.   return self;
  386.   
  387. }
  388.  
  389. // -------------------------------------------------------------------------------------
  390. // process data record (overridden by subclass)
  391. - (BOOL)dataRecord:(char*)buff len:(int)len fromPort:(int)portNum
  392. {
  393.   if (dataHandler                                                       &&
  394.       [dataHandler respondsTo:@selector(dataRecord:len:fromPort:)]      &&
  395.       (dataHandler != self)                                               ) {
  396.     [dataHandler dataRecord:buff len:len fromPort:portNum];
  397.     return YES;
  398.   }
  399.   return NO;
  400. }
  401.  
  402. // -------------------------------------------------------------------------------------
  403. // start read loop thread (from main thread)
  404. - forkPortListener
  405. {
  406.   mutexLock(forkMutex);
  407.   if (!isListening) {
  408.     portThread = cthread_fork((cthread_fn_t)readLoop, (any_t)self);
  409.     cthread_detach(portThread);
  410.     isListening = YES;
  411.   }
  412.   mutexUnlock(forkMutex);
  413.   return self;
  414. }
  415.   
  416. @end
  417.